package commons.conf.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.ResourceUtils;

import commons.BizException;
import commons.conf.ResourceApplication;
import commons.conf.ResourceController;
import commons.conf.ResourceDomain;
import commons.conf.ResourceDomainField;
import commons.conf.ResourceService;
import commons.conf.ResourceView;
import commons.conf.base.BaseResourceDomain;
import commons.conf.base.BaseResourceDomainField;
import commons.conf.base.BaseResourceDomainFieldId;

/**
 * info:::http://scholers.iteye.com/blog/1326842
 * @author jkyang
 *
 */
public class XmlResourceApplication implements ResourceApplication{

	private static Log log = LogFactory.getLog(XmlResourceApplication.class);
	
	private Map<String, ResourceDomain> domainMap = new HashMap<String, ResourceDomain>();

	private List<String> locations = new ArrayList<String>();
	
	public void init() {
		if (locations != null) {
			for (String path : locations) {
				try {
					File file = ResourceUtils.getFile(path);
					loadXml(file);
				} catch (FileNotFoundException e) {
					e.printStackTrace();
					throw new BizException(e);
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void loadXml(File file) {
		SAXReader reader = new SAXReader();
		try {
			Document document = reader.read(new FileInputStream(file));
			Element root = document.getRootElement();
			String moduleName = root.attributeValue("name");
			log.debug(moduleName);
			for (Iterator i = root.elementIterator(); i.hasNext();) {
				Element table = (Element) i.next();
				
				BaseResourceDomain d = loadTable(table, moduleName);
				if (domainMap.containsKey(d.getFullName())) {
					throw new BizException(d.getFullName() + " has exists. please check.");
				}
				log.debug(d);
				domainMap.put(d.getFullName(), d);
			}
//			log.debug(domainMap);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new BizException(e);
		} catch (DocumentException e) {
			e.printStackTrace();
			throw new BizException(e);
		}
	}

	public void validate() {
		// TODO 检查XML文件中冲突的配置项,例如外键，名称相同等
	}

	private ResourceDomain getDomainByTableId(String tid) {
		for (ResourceDomain d : domainMap.values()) {
			if(d.getTableName().equalsIgnoreCase(tid)) {
				return d ;
			}
		}
		return null;
	}
	
	
	
	private void prepareTableMapping() {
		// TODO 加载表间关系
		for (String domain : domainMap.keySet()) {
			ResourceDomain rd = domainMap.get(domain);
			for (ResourceDomainField field : rd.getFieldMap().values()) {
				if(field.isForeign()) {
					String tableName = field.getForeignRefDomainName();
					String columnName = field.getForeignRefDomainFieldName();
					ResourceDomain rr = getDomainByTableId(tableName);
					if(rr == null) {
						throw new BizException("can not find table " + tableName + ".");
					}
					ResourceDomainField rf = rr.findFieldByColumnId(columnName);
					if(rf == null) {
						throw new BizException("can not find column " + columnName + " in table " + tableName);
					}
					field.setJavaType(rr.getFullName());
				}else {
					field.setJavaType(TypeMapping.getMappingTypeString(field.getType()));
				}
			}
		}
		
	}
	
	@SuppressWarnings("unchecked")
	public BaseResourceDomain loadTable(Element table, String moduleName) {
		BaseResourceDomain domain = new BaseResourceDomain();
		domain.setModuleName(moduleName);
		domain.setTableName(table.attributeValue("id"));
		// TODO加入些缺省值
		domain.setDomainName(table.attributeValue("name"));
		for (Iterator i = table.elementIterator(); i.hasNext();) {
			Element field = (Element) i.next();
			BaseResourceDomainField f = loadField(field, domain);
			if (f != null) {
				domain.getFieldMap().put(f.getName(), f);
			}
		}
		return domain;
	}

	public BaseResourceDomainField loadField(Element field, BaseResourceDomain domain) {
		log.debug(field.getName());
		BaseResourceDomainField f = null;
		if (field.getName().equalsIgnoreCase("id")) {
			f = new BaseResourceDomainFieldId();
		} else {
			f = new BaseResourceDomainField();
		}
		// TODO
		String id = field.attributeValue("id");
		String name = field.attributeValue("name");
		String type = field.attributeValue("type");
		String length = field.attributeValue("length");
		// String scale = field.attributeValue("scale");
		String notNull = field.attributeValue("notNull");
		String unique = field.attributeValue("unique");
		String foreign = field.attributeValue("foreign");
		String foreignRef = field.attributeValue("foreignRef");

		f.setId(id);
		if(StringUtils.isNotEmpty(name)) {
			f.setName(name);
		}else {
			f.setName(JdbcUtils.convertUnderscoreNameToPropertyName(id));
		}
		f.setType(type);
		if (length != null) {
			if (length.indexOf(",") > -1) {
				String[] arr = length.split(",");
				f.setLength(Integer.valueOf(arr[0]));
				f.setScale(Integer.valueOf(arr[1]));
			} else {
				f.setLength(Float.valueOf(length).intValue());
			}
		}
		if (notNull != null) {
			f.setNotNull(Boolean.valueOf(notNull));
		}
		if (unique != null) {
			f.setUnique(Boolean.valueOf(unique));
		}
		if (foreign != null) {
			f.setForeign(Boolean.valueOf(foreign));
		}
		if (foreignRef != null) {
			f.setForeignRef(foreignRef);
		}
		return f;
	}

	public List<String> getLocations() {
		return locations;
	}

	public void setLocations(List<String> locations) {
		this.locations = locations;
	}


	public void setDomainMap(Map<String, ResourceDomain> domainMap) {
		this.domainMap = domainMap;
	}

	public void afterPropertiesSet() throws Exception {
		this.init();
		prepareTableMapping();
		validate();
	}

	public static void main(String[] args) throws Exception {
		XmlResourceApplication c = new XmlResourceApplication();
		c.getLocations().add("classpath:commons/conf/example.xml");
		c.afterPropertiesSet();
	}
	
	public List<ResourceController> getAllController() {
		// TODO Auto-generated method stub
		return null;
	}

	public List<ResourceDomain> getAllDomains() {
		List<ResourceDomain> array = new ArrayList<ResourceDomain>();
		
		//ordering for class loader.
		
		Set<ResourceDomain> nof = new HashSet<ResourceDomain>();
		
		Set<ResourceDomain> hf = new HashSet<ResourceDomain>();
		
		for (ResourceDomain rd : this.getDomainMap().values() ) {
			if(rd.hasForeign()) {
				rd.getFullName();
				rd.getForeignFields();
				hf.add(rd);
			}else {
				nof.add(rd);
			}
		}
		
		
		array.addAll(nof);
		array.addAll(hf);
		return array;
	}

	public List<ResourceService> getAllService() {
		// TODO Auto-generated method stub
		return null;
	}

	public List<ResourceView> getAllViews() {
		// TODO Auto-generated method stub
		return null;
	}

	public Map<String, ResourceDomain> getDomainMap() {
		return domainMap;
	}

}
